<template>
  <div class="particle-setting-iframe">
    <div id="particleCesiumContainerBox" ref="particleCesiumContainerRef"></div>
    <!-- 左侧树 -->
    <div class="tree-panel-wrap panel-wrapper" :class="treeVisible ? 'active' : ''">
      <div class="panel-inner">
        <div class="panel-handler" @click="toggleTreeVisible">
          <SvgIcon icon-class="ic_展开" />
        </div>
        <div class="panel">
          <div class="panel-body">
            <el-tree
              ref="menuTree"
              :data="treeData"
              node-key="name"
              draggable
              show-checkbox
              :show-file-list="false"
              :props="defaultProps"
              :default-expand-all="true"
              :expand-on-click-node="false"
              @check-change="handleCheckChange"
              @node-click="handleNodeClick"
            >
              <template #default="{ node, data }">
                <div class="custom-tree-node">
                  <span>{{ node.label }}</span>

                  <div class="more-wrap">
                    <el-popover
                      :ref="setOpePopoverRef"
                      placement="bottom"
                      trigger="click"
                      :width="180"
                      popper-class="particle-ope-popover"
                    >
                      <template #reference>
                        <SvgIcon icon-class="more" />
                      </template>
                      <div class="node-ope-ul">
                        <div
                          v-show="deleteCondition(node, data)"
                          class="node-ope-li"
                          @click="deleteEffectAndTreeNode(node, data)"
                        >
                          删除
                        </div>
                        <div
                          v-show="addDirectoryCondition(node, data)"
                          class="node-ope-li"
                          @click="addTreeNode"
                        >
                          添加实体
                        </div>
                        <div
                          v-show="addEffectCondition(node, data)"
                          class="node-ope-li"
                          @click="addParticle(node)"
                        >
                          添加粒子
                        </div>
                        <div
                          v-show="renameCondition(node, data)"
                          class="node-ope-li"
                          @click="renameParticle(node)"
                        >
                          重命名
                        </div>
                        <div
                          v-show="addModelCondition(node, data)"
                          class="node-ope-li"
                          @click="triggerEntityFileUpload"
                        >
                          导入实体
                        </div>
                        <div
                          v-show="addEffectCondition(node, data)"
                          class="node-ope-li"
                          @click="triggerFxFileUpload"
                        >
                          导入粒子
                        </div>
                        <div
                          v-show="addEffectCondition(node, data)"
                          class="node-ope-li"
                          @click="moveEntity(node)"
                        >
                          移动实体
                        </div>
                        <div
                          v-show="addEffectCondition(node, data)"
                          class="node-ope-li"
                          @click="stopEntity(node)"
                        >
                          停止移动
                        </div>
                        <div class="node-ope-li" @click="exportCurrentJson">导出配置</div>
                      </div>
                    </el-popover>
                  </div>
                </div>
              </template>
            </el-tree>
          </div>
        </div>
      </div>
    </div>
    <!-- 特效基本信息 -->
    <div class="effect-basic-wrap panel-wrapper">
      <div class="panel-inner">
        <div class="panel">
          <div class="panel-header">特效基本信息</div>
          <div class="panel-body">
            <el-form :model="effectInfo" label-position="top">
              <el-form-item
                label="特效名称"
                prop="effectName"
                :rules="[{ required: true, message: '特效名称不能为空' }]"
              >
                <el-input
                  v-model="effectInfo.effectName"
                  placeholder="设置一个特效名称，不超过50个字符"
                />
              </el-form-item>
              <el-form-item label="特效示例图">
                <div class="cover-wrap">
                  <div class="upload-wrap">
                    <el-upload
                      :show-file-list="false"
                      action="#"
                      :before-upload="beforeCoverUpload"
                    >
                      <div class="cover">
                        <el-image
                          v-if="effectInfo.effCover"
                          :src="retFilePath(effectInfo.effCover)"
                          fit="cover"
                        />
                        <SvgIcon v-else icon-class="ic_plus" />
                      </div>
                    </el-upload>
                    <div v-if="effectInfo.effCover" class="hover-wrap">
                      <el-button @click="effectInfo.effCover = ''">删&ensp;除</el-button>
                    </div>
                  </div>

                  <div class="cover-tip">将当前视角截图为特效示例图。</div>
                  <el-button type="primary" @click="setCover">设置特效示例图</el-button>
                </div>
              </el-form-item>
            </el-form>
          </div>
        </div>
      </div>
    </div>
    <!-- 右侧属性面板 -->
    <div class="attr-wrap panel-wrapper" :class="attrVisible ? 'active' : ''">
      <div class="panel-inner">
        <div class="panel-handler" @click="toggleAttrVisible">
          <SvgIcon icon-class="ic_展开" />
        </div>
        <div class="panel">
          <div class="panel-body">
            <EntityAttr
              v-if="currentType === EffectType.ENTITY || currentType === EffectType.GLTFMODEL"
              :panel-data="cimPanelData"
              @update="updateTransform"
            />
            <CimAttr
              v-if="
                currentType === EffectType.CIMPARTICLESYSTEM ||
                currentType === EffectType.U3DPARTICLESYSTEM
              "
              :panel-data="cimPanelData"
              @update="updateTransform"
              @update-effect="updateEffect"
              @choose-pic="choosePicVisible = true"
            />
            <FakeAttr
              v-if="currentType === EffectType.FAKEPARTICLESYSTEM"
              :panel-data="fakePanelData"
              @update-effect="updateEffect"
            />
          </div>
        </div>
      </div>
    </div>
    <!-- 添加特效弹窗 -->
    <AddParticleDialog
      v-if="dialogVisible"
      v-model="dialogVisible"
      :node-name="currentName"
      @close-handle="addParticleDialogClose"
    />
    <!-- 重命名弹窗 -->
    <Rename
      v-if="renameDialogVisible"
      v-model="renameDialogVisible"
      :node-name="currentTreeNode.vName"
      @close-handle="renameDialogClose"
    />
    <!-- 导入实体 -->
    <input
      ref="entityFileUpload"
      type="file"
      style="display: none"
      @change="handleEntityFileUpload"
    />
    <!-- 导入粒子 -->
    <input ref="fxFileUpload" type="file" style="display: none" @change="handleFxFileUpload" />

    <SvgIcon v-if="screenShotIconVisible" class="screenShotIcon" icon-class="封面框"> </SvgIcon>
  </div>
  <!-- 插入图片 -->
  <ChoosePic
    v-if="choosePicVisible"
    v-model="choosePicVisible"
    @close-handle="choosePicClose"
    @sel-pic="picChange"
  />
  <!-- 插入模型 -->
  <ChooseResource
    v-if="chooseResourceVisible"
    v-model="chooseResourceVisible"
    @close-handle="chooseModelClose"
  />
</template>

<script setup lang="ts">
import { inject } from 'vue';
import EntityAttr from './entity-attr.vue';
import CimAttr from './cim-attr.vue';
import FakeAttr from './fake-attr.vue';
import AddParticleDialog from './add-particle.vue';
import Rename from './rename.vue';
import ChoosePic from '@/components/choose-pic.vue';
import ChooseResource from '@/components/choose-resource.vue';
import { messageUtil } from '@jl/element-plus/utils';
import { commonUpload } from '@/api/common';
import { CommonUploadResp } from '@/api/common/types';
import { myHtml2canvas, confirmAgain, retFilePath } from '@/utils';
import { debounce } from 'lodash';

const EffectType = {
  U3DPARTICLESYSTEM: 'u3d_particleSystem',
  CIMPARTICLESYSTEM: 'cim_particleSystem',
  FAKEPARTICLESYSTEM: 'fake_particleSystem',
  ENTITY: 'entity',
  GLTFMODEL: 'gltfmodel',
};
const DefaultEntityData = {
  name: 'empty',
  type: 'entity',
  views: [
    {
      enable: true,
      name: 'empty',
      type: 'gltfmodel',
      createType: 'primitive',
      positions: [[121.54525638, 31.21743679, 10]],
      rotation: [0, 0, 0],
      dataSourceUrl: retFilePath('comm/effects/defParticle/empty.gltf'),
      scale: 1,
      enablePbr: true,
      shadowsType: 1,
      iblConfig: {
        environmentMaps: '/cimupresource/image/environment/noon.ktx2',
      },
      show: true,
    },
  ],
};

const DefaultCimEffectData = {
  enable: true,
  show: true,
  name: '粒子',
  type: 'cim_particleSystem',
  emitterStr: 'CircleEmitter',
  emissionRate: 10,
  bursts: [],
  loop: true,
  startScale: 0,
  endScale: 1,
  image: retFilePath('comm/effects/defParticle/smoke.png'),
  minimumImageSizeWidth: 16,
  minimumImageSizeHeight: 16,
  maximumImageSizeWidth: 50,
  maximumImageSizeHeight: 50,
  sizeInMeters: false,
  minimumSpeed: 125.31,
  maximumSpeed: 22,
  lifetime: 5,
  minimumParticleLife: 5,
  maximumParticleLife: 5,
  minimumMass: 1,
  maximumMass: 1,
  circleRadius: 166.5,
  sphereRadius: 200,
  coneAngle: 45,
  box_x: 100,
  box_y: 100,
  box_z: 100,
  startColor: 'rgba(255, 255, 255, 1)',
  endColor: 'rgba(255, 255, 255, 1)',
  // longitude: 121.54525638,
  // latitude: 31.21743679,
  // height: 10,
  offset: [0, 0, 0],
};

const DefaultFakeEffectData = {
  enable: true,
  name: '粒子',
  type: 'fake_particleSystem',
  emitterStr: 'CircleWave',
  speed: 10,
  semiMinorAxis: 300,
  semiMajorAxis: 300,
  circleCount: 3,
  color: '#ffffff',
  // longitude: 121.54525638,
  // latitude: 31.21743679,
  // height: 50,
  offset: [0, 0, 0],
};

let cimNumber = 0;
const intial_map_coord = [121.54425638, 31.20643679, 1000];
let exportJson = '';

const choosePicVisible = ref<boolean>(false);
const chooseResourceVisible = ref<boolean>(false);
const entityFileUpload = ref();
const fxFileUpload = ref();
const menuTree = ref();
const treeVisible = ref<boolean>(true);
const attrVisible = ref<boolean>(false);
const treeData = ref<any>([
  // {
  //   vName: 'Root',
  //   name: 'Root',
  //   type: 'root',
  //   isDirectory: true,
  //   children: []
  // }
]);
const defaultProps = ref<any>({
  children: 'children',
  label: 'vName',
});
// 当前特效类型
const currentType = ref<string>('');
const currentName = ref<string>('');
let currentMoreNode = null as any;
// 当前点击的树节点
const currentTreeNode = ref<any>(null);
// 属性面板
const cimPanelData = ref<any>({
  show: true,
  name: '',
  type: 'cim_particleSystem',
  emitterStr: 'CircleEmitter',
  circleRadius: 200,
  sphereRadius: 100,
  coneAngle: 45,
  box_x: 100,
  box_y: 100,
  box_z: 100,
  emissionRate: 10,
  bursts: [],
  loop: true,
  startScale: 0,
  endScale: 0,
  minimumImageSizeWidth: 2,
  minimumImageSizeHeight: 2,
  maximumImageSizeWidth: 0,
  maximumImageSizeHeight: 0,
  sizeInMeters: false,
  minimumSpeed: 0,
  maximumSpeed: 0,
  lifetime: 0,
  minimumParticleLife: 0,
  maximumParticleLife: 0,
  minimumMass: 0,
  maximumMass: 0,
  startColor: 'rgba(255, 0, 0, 1)',
  endColor: 'rgba(255, 0, 0, 1)',
  image: '',
  longitude: 121.54525638,
  latitude: 31.21743679,
  height: 10,
  position_x: 0,
  position_y: 0,
  position_z: 0,
  rotation_x: 0,
  rotation_y: 0,
  rotation_z: 0,
  scale_x: 1,
  offset: [],
  gravityX: 0,
  gravityY: 0,
  gravityZ: 0,
});

const fakePanelData = ref<any>({
  name: '',
  type: 'fake_particleSystem',
  emitterStr: 'CircleWave',
  speed: 10,
  semiMinorAxis: 300,
  semiMajorAxis: 300,
  circleCount: 3,
  color: '#ffff00',
  longitude: 26.546502426730826,
  latitude: 177.68321045634773,
  height: 9943,
  offset: [],
});
const dialogVisible = ref<boolean>(false);
const renameDialogVisible = ref<boolean>(false);
const effectType = ref<any>([
  {
    label: 'cim_particleSystem',
    value: 'cim_particleSystem',
  },
  {
    label: 'fake_particleSystem',
    value: 'fake_particleSystem',
  },
]);
const modelNodes = ref<any>([]);
const chooseEffectType = ref<string>('');
const newFxOwner = ref<string>('');
const doubleClickFlag = ref<boolean>(false);
const movingListeners = ref<any>([]);
const screenShotIconVisible = ref<boolean>(false);

// 特效信息
const effectInfo = ref<any>({
  effectName: '',
  effCover: '',
});

const deleteCondition = (node: any, data: any) => {
  // 非根目录叶子节点和目录节点 可删除
  return !['root', 'entity'].includes(data.type);
};
const addEffectCondition = (node: any, data: any) => {
  // 目录可添加特效
  return data.isDirectory;
};
const renameCondition = (node: any, data: any) => {
  return data.type === EffectType.CIMPARTICLESYSTEM || data.type === EffectType.FAKEPARTICLESYSTEM;
};
const addDirectoryCondition = (node: any, data: any) => {
  // 目录可添加特效
  return data.isDirectory && !data.isModel;
};
const addModelCondition = (node: any, data: any) => {
  return data.isDirectory && !data.isModel;
};

const selEffect: any = inject('selEffect');
const particleCesiumContainerRef = ref();

const opePopoverRefs = [] as any;
const setOpePopoverRef = (el: any) => {
  if (el) {
    opePopoverRefs.push(el);
  }
};

onMounted(() => {
  const { cimSettingInstance } = window;
  nextTick(() => {
    cimSettingInstance.init({
      config: {
        gisContainer: particleCesiumContainerRef.value,
        options: {
          animation: true,
          timeline: true,
          clock: true,
          hours: 11,
          delay: 0,
        },
        map: '谷歌',
        mapObject: {
          view: {
            longitude: intial_map_coord[0],
            latitude: intial_map_coord[1],
            height: intial_map_coord[2],
            duration: 3,
            heading: 0.259952026165909,
            pitch: -0.65354559962687812,
            maxDistance: 10000000,
            initViewStatically: true,
          },
          layers: [
            {
              enable: true,
              index: 0,
              type: 'base',
              mapType: 'ARCGIS',
              dataSourceUrl:
                'https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer',
              maximumLevel: 18,
              minimumLevel: 1,
            },
          ],
        },
      },
    });
    cimSettingInstance.eventApi.addEventListener('set_data_complete', initLoad);
    window.addEventListener('keydown', quickOperation);
  });
});

onBeforeUnmount(() => {
  const { cimSettingInstance } = window;
  window.removeEventListener('keydown', quickOperation);
  cimSettingInstance.eventApi.removeEventListener('set_data_complete', initLoad);
});

function initLoad() {
  const { cimSettingInstance } = window;
  let config = null as any;
  const scene_config = selEffect.value.configJson;
  // const scene_config = '';
  effectInfo.value.effectName = selEffect.value.effectId ? selEffect.value.effectName : '';
  effectInfo.value.effCover = selEffect.value.effCover;
  if (!scene_config) {
    config = JSON.parse(JSON.stringify(DefaultEntityData));
    config.vName = 'Root';
    config.name = 'Root';
    config.views[0].name = config.name;
    cimSettingInstance.entityApi.addEntity(config);
    treeData.value.push({
      name: config.name,
      vName: config.vName,
      isDirectory: true,
      isModel: true,
      isEmpty: true,
      type: 'entity',
      labelColor: 'rgb(40, 200, 40)',
      children: [],
    });
    currentTreeNode.value = treeData.value[0];
    nextTick(() => {
      menuTree.value.setCurrentKey(config.name);
      menuTree.value.setChecked(config.name, true);
      const node = menuTree.value.getNode(config.name);
      handleNodeDoubleClick(node.data, node);
      handleNodeClick(node.data, node);
    });
  } else {
    config = JSON.parse(scene_config);
    if (!config.vName) {
      config.vName = config.name;
      cimSettingInstance.entityApi.addEntity(config);
      const entity = cimSettingInstance.entityApi.getEntity(config.name);
      const parts = [] as any;
      entity._viewComponent._part.forEach((name: string) => {
        const p = cimSettingInstance.api.getLayer(name);
        const p_config = p._config;
        if (!p_config.vName) {
          p_config.vName = p_config.name;
        }
        parts.push(p_config);
        cimSettingInstance.api.setOrbitControlByInstance(p, name, updateTransformPanelInfo);
      });
      const particles = [] as any;
      entity._viewComponent._particle.forEach((name: string) => {
        const pl = cimSettingInstance.api.getLayer(name);
        const pd = cimSettingInstance.api.getParticleSystemData(name);
        if (!pl.config.vName) {
          pl.vName = pl.name;
        } else {
          pl.vName = pl.config.vName;
        }
        pd.vName = pl.vName;
        particles.push(pd);
        cimSettingInstance.api.setOrbitControlByInstance(pl, name, updateTransformPanelInfo);
      });
      cimSettingInstance.api.setOrbitControlByInstance(entity, config.name, () => {});
      cimSettingInstance.api.refreshOrbitController();
      cimSettingInstance.api.changeOrbitControlMode('translation');
      const data = {
        name: config.name,
        vName: config.vName ? config.vName : config.name,
        isDirectory: true,
        isModel: true,
        type: 'entity',
        labelColor: 'rgb(40, 200, 40)',
        children: [...parts, ...particles],
      };
      treeData.value.push(data);
      currentTreeNode.value = treeData.value[0];
      nextTick(() => {
        menuTree.value.setCurrentKey(config.name);
        menuTree.value.setChecked(config.name, true);
        parts.forEach((p: any) => {
          menuTree.value.setChecked(p.name, true);
        });
        particles.forEach((p: any) => {
          menuTree.value.setChecked(p.name, p.show);
        });
        const node = menuTree.value.getNode(config.name);
        handleNodeDoubleClick(data, node);
        handleNodeClick(node.data, node);
      });
    }
  }
}

const setCover = debounce(
  () => {
    screenShotIconVisible.value = true;
    myHtml2canvas(particleCesiumContainerRef.value).then(async (file: any) => {
      const res: CommonUploadResp = await commonUpload(file);
      effectInfo.value.effCover = res.files[0].refAddress;
      setTimeout(() => {
        screenShotIconVisible.value = false;
      }, 400);
    });
  },
  300,
  {
    leading: true,
    trailing: false,
  }
);

// 读取文件 导入json 此方式会有绿盾问题
function openFile(file: any) {
  const fileReader: any = new FileReader();
  fileReader.onload = () => {
    if (fileReader.result) {
      const configs = JSON.parse(fileReader.result);
      // 反复导入配置不做叠加
      menuTree.value.remove(treeData.value[0].children);
      removeParticleSystem(treeData.value[0].children);
      if (Array.isArray(configs)) {
        treeData.value[0].children.push(...configs);
      } else {
        treeData.value[0].children.push(configs);
      }
      const visibleKeys: any = [];
      collectDefaultCheckedKeys(treeData.value, visibleKeys);
      // 根据属性 更新tree default checked
      nextTick(() => {
        menuTree.value.setCheckedKeys(visibleKeys, true);
      });
      createParticleSystem(configs);
    }
  };
  fileReader.onerror = (err: any) => {
    console.log(err);
  };
  fileReader.readAsText(file.raw);
}

function collectDefaultCheckedKeys(configs, visibleKeys = []) {
  if (!configs) {
    return;
  }
  configs.forEach((item) => {
    if (item.show) {
      visibleKeys.push(item.name);
    } else {
      collectDefaultCheckedKeys(item.children, visibleKeys);
    }
  });
}

function findFxNodes(node: any) {
  let result = [];
  if (
    node.type === 'cim_particleSystem' ||
    node.type === 'fake_particleSystem' ||
    node.type === 'gltfmodel'
  ) {
    result.push(node);
  }
  if (node.children) {
    for (let i = 0; i < node.children.length; i++) {
      result = result.concat(findFxNodes(node.children[i]));
    }
  }
  return result;
}

function moveEntity(node: any) {
  const { cimSettingInstance, Cesium } = window;
  currentTreeNode.value = node.data;
  const layer =
    cimSettingInstance.entityApi.getEntity(currentTreeNode.value.name) ||
    cimSettingInstance.api.getLayer(currentTreeNode.value.name);
  if (layer) {
    const node = currentTreeNode.value;
    node.startMoving = true;
    node.angle = 0;
    node.circleRadius = 50;
    node.angularSpeed = Cesium.Math.toRadians(10);
    node.localPosition = new Cesium.Cartesian3();
    node.previousTime = null;
    const modelMatrix = layer.modelMatrix;
    const name = currentTreeNode.value.name;
    const data = menuTree.value.getNode(name)?.data;

    const newListener = function (scene: any, time: any) {
      if (!node.previousTime) {
        node.previousTime = time.secondsOfDay;
        return;
      }
      const currentTime = time.secondsOfDay;
      const deltaTime = currentTime - node.previousTime;
      node.angle += deltaTime * node.angularSpeed;
      node.angle = node.angle % 6.283185307179;
      node.localPosition.x = node.circleRadius - node.circleRadius * Math.cos(node.angle);
      node.localPosition.y = node.circleRadius * Math.sin(node.angle);
      node.localPosition.z = 0;
      const fixedFramePosition = Cesium.Matrix4.multiplyByPoint(
        modelMatrix,
        node.localPosition,
        new Cesium.Cartesian3()
      );
      const cartographic = Cesium.Cartographic.fromCartesian(fixedFramePosition);
      cimSettingInstance.api.updateEffectModel({
        position: {
          x: Cesium.Math.toDegrees(cartographic.longitude),
          y: Cesium.Math.toDegrees(cartographic.latitude),
          z: cartographic.height,
        },
        rotation: {
          x: 0,
          y: 0,
          z: 0,
        },
        scale: 1,
      });
      cimSettingInstance.api.refreshOrbitController();
      node.previousTime = currentTime;
    };
    cimSettingInstance.eventApi.addEventListener('scene_postrender', newListener);
    node.moveCallback = newListener;
    movingListeners.value.push(newListener);
  } else {
    console.error('invalid layer.');
  }
}

function stopEntity(node: any) {
  const { cimSettingInstance } = window;
  currentTreeNode.value = node.data;
  if (currentTreeNode.value.moveCallback) {
    cimSettingInstance.api.cimViewer.scene.postRender.removeEventListener(
      currentTreeNode.value.moveCallback
    );
  }
}

// 导出json
function exportCurrentJson() {
  closePopover();
  saveJSON(currentTreeNode.value, currentTreeNode.value.name + '.json');
}

// 导出配置
function saveJSON(data: any, filename?: string) {
  const { cimSettingInstance } = window;
  if (!data) {
    alert('保存的数据为空');
    return;
  }
  let configData;

  switch (data.type) {
    case 'entity':
      const entity = cimSettingInstance.entityApi.getEntity(data.name);
      const viewDataList = entity.entityData.viewDataList;
      const componentList = entity.entityData.componentList;
      // componentList.forEach(com => {
      // com.name = com.vName
      // })
      configData = {
        name: data.name,
        enable: entity.enable,
        type: data.type,
        views: viewDataList,
        components: componentList,
      };
      break;
    case 'root':
      let result = [];
      for (let i = 0; i < treeData.value.length; i++) {
        result = result.concat(findFxNodes(treeData.value[i]));
      }
      configData = [];
      for (let i = 0; i < result.length; i++) {
        const layer = cimSettingInstance.api.getLayer(result[i].name);
        if (layer) {
          configData.push(layer.config);
        }
      }
      break;
    default:
      const layer = cimSettingInstance.api.getLayer(data.name);
      if (layer) {
        configData = layer.config;
      }
      break;
  }
  const jsonData = JSON.stringify(configData);
  // console.log('导出json---', jsonData);
  exportJson = jsonData;
  if (filename) {
    const blob = new Blob([jsonData], { type: 'text/json' });
    const a = document.createElement('a');
    a.download = filename;
    a.href = window.URL.createObjectURL(blob);
    a.click();
    window.URL.revokeObjectURL(a.href);
  }
}

function flyTo(longitude: number, latitude: number, altitude: number, height: number) {
  const { cimSettingInstance } = window;
  if (longitude && latitude && altitude) {
    cimSettingInstance.api.lookModel(
      longitude,
      latitude,
      altitude,
      0,
      -0.65354559962687812,
      height,
      1.5
    );
  }
}

// 树节点点击
function handleNodeClick(data: any, node: any) {
  const { cimSettingInstance } = window;
  if (doubleClickFlag.value) {
    doubleClickFlag.value = false;
    handleNodeDoubleClick(data, node); // Handle the double click event
  } else {
    doubleClickFlag.value = true;
    setTimeout(() => {
      if (doubleClickFlag.value) {
        doubleClickFlag.value = false;
      }
    }, 300); // 300ms delay; adjust if necessary
  }
  if (data.type === 'root') return;
  currentTreeNode.value = data;
  currentName.value = data.name;
  if (data.type !== currentType.value) {
    currentType.value = data.type;
  }
  switch (data.type) {
    case 'entity':
      const entity = cimSettingInstance.entityApi.getEntity(data.name);
      if (entity) {
        cimSettingInstance.api.setOrbitControlByInstance(
          entity,
          data.name,
          updateTransformPanelInfo
        );
      }
      break;
    case 'gltfmodel':
      const model = cimSettingInstance.api.getLayer(data.name);
      cimSettingInstance.api.setOrbitControlByInstance(model, data.name, updateTransformPanelInfo);
      break;
    default:
      const particleSystem = cimSettingInstance.api.getParticleSystem(data.name);
      if (particleSystem) {
        assignPanelData();
        cimSettingInstance.api.setOrbitControlByInstance(
          particleSystem,
          data.name,
          updateTransformPanelInfo
        );
      }
      break;
  }
  cimSettingInstance.api.refreshOrbitController();
  cimSettingInstance.api.changeOrbitControlMode('translation');
}

// 树节点复选框点击
function handleCheckChange(data: any, checked: boolean, indeterminate: boolean) {
  const { cimSettingInstance } = window;
  const layer = cimSettingInstance.api.getLayer(data.name);
  if (layer && layer.visible !== undefined) {
    layer.config.show = checked || indeterminate;
    layer.show(checked || indeterminate);
  }
}

// 添加图层
function addTreeNode() {
  const { cimSettingInstance } = window;
  if (currentTreeNode.value) {
    const config = JSON.parse(JSON.stringify(DefaultEntityData));
    config.vName = config.name + ` ${(cimNumber += 1)}`;
    config.name = cimSettingInstance.api.uuid();
    config.views[0].name = config.name;
    cimSettingInstance.entityApi.addEntity(config);
    currentTreeNode.value.children.push({
      name: config.name,
      vName: config.vName,
      isDirectory: true,
      isModel: true,
      isEmpty: true,
      type: 'entity',
      labelColor: 'rgb(40, 200, 40)',
      children: [],
    });
    closePopover();
    nextTick(() => {
      menuTree.value.setCurrentKey(config.name);
      menuTree.value.setChecked(config.name, true);
      const node = menuTree.value.getNode(config.name);
      modelNodes.value.push(node.data);
      handleNodeClick(node.data, node);
    });
  }
}

// 添加单个特效和树节点
function addEffectAndTreeNode(config?: any) {
  const { cimSettingInstance } = window;
  let defaultConfig = null as any;
  if (config) {
    defaultConfig = config;
    if (!defaultConfig.vName) {
      defaultConfig.vName = defaultConfig.name;
    }
  } else {
    getMaxCimNumber(currentMoreNode.children);
    switch (currentType.value) {
      case EffectType.CIMPARTICLESYSTEM:
        defaultConfig = Object.assign({}, DefaultCimEffectData);
        break;
      case EffectType.FAKEPARTICLESYSTEM:
        defaultConfig = Object.assign({}, DefaultFakeEffectData);
        break;
      case EffectType.U3DPARTICLESYSTEM:
        break;
    }
    defaultConfig.vName = defaultConfig.name + `${(cimNumber += 1)}`;
    defaultConfig.name = cimSettingInstance.api.uuid();
  }

  if (!defaultConfig.longitude) {
    const screenSize = cimSettingInstance.api.getScreenSize();
    const position = cimSettingInstance.api.screenToDegrees({
      x: screenSize.x / 2,
      y: screenSize.y / 2,
    });
    defaultConfig.longitude = position[0];
    defaultConfig.latitude = position[1];
    defaultConfig.height = position[2];
  }

  if (currentMoreNode.isModel) {
    defaultConfig.owner = currentMoreNode.name;
  } else if (newFxOwner.value) {
    defaultConfig.owner = newFxOwner.value;
    newFxOwner.value = '';
  }
  const instance = cimSettingInstance.api.createParticleSystem(defaultConfig);
  let parentNode = currentMoreNode;
  if (defaultConfig.owner) {
    const entity = cimSettingInstance.entityApi.getEntity(defaultConfig.owner);
    entity._viewComponent._particle.push(defaultConfig.name);
    entity.entityData.componentList.push(instance.config);
    const node = menuTree.value.getNode(defaultConfig.owner);
    parentNode = node.data;
  }
  cimSettingInstance.api.setOrbitControlByInstance(
    instance,
    defaultConfig.name,
    updateTransformPanelInfo
  );
  cimSettingInstance.api.refreshOrbitController();
  cimSettingInstance.api.changeOrbitControlMode('translation');
  const panelData: any = {};
  Object.assign(panelData, defaultConfig);
  // delete panelData.offset
  // 数据同步 名称同步
  assignPanelData(panelData);
  parentNode.children.push(panelData);
  currentName.value = panelData.name;
  nextTick(() => {
    menuTree.value.setCurrentKey(panelData.name);
    menuTree.value.setChecked(panelData.name, true, true);
  });
}
function triggerEntityFileUpload() {
  entityFileUpload.value.click();
}
function triggerFxFileUpload() {
  fxFileUpload.value.click();
}
function handleEntityFileUpload(event: any) {
  const file = event.target.files[0];
  const acceptedFileTypes = ['json'];
  const fileExtension = file.name.split('.').pop().toLowerCase();

  if (acceptedFileTypes.includes(fileExtension)) {
    addModelNode(file);
  } else {
    console.error('Invalid file type');
  }
  event.target.value = null;
}

function handleFxFileUpload(event: any) {
  const { cimSettingInstance } = window;
  const file = event.target.files[0];
  const acceptedFileTypes = ['json'];
  const fileExtension = file.name.split('.').pop().toLowerCase();
  if (acceptedFileTypes.includes(fileExtension)) {
    const fileReader: any = new FileReader();
    fileReader.onload = () => {
      if (fileReader.result) {
        const config = JSON.parse(fileReader.result);
        config.owner = 'Root';
        const entity = cimSettingInstance.entityApi.getEntity('Root');
        config.longitude = entity.entityData.viewDataList[0].positions[0][0];
        config.latitude = entity.entityData.viewDataList[0].positions[0][1];
        config.height = 0;
        config.vName = `粒子${(cimNumber += 1)}`;
        addEffectAndTreeNode(config);
      }
    };
    fileReader.onerror = (err: any) => {
      console.log(err);
    };
    fileReader.readAsText(file);
  } else {
    console.error('Invalid file type');
  }
  event.target.value = null;
}

function handleNodeDoubleClick(data, node) {
  const { cimSettingInstance } = window;
  const layer = cimSettingInstance.api.getLayer(data.name).config;
  console.log('Double clicked:', layer);
  flyTo(cimPanelData.value.longitude, cimPanelData.value.latitude, cimPanelData.value.height, 600);
}

function addModelNode(file) {
  const { cimSettingInstance } = window;
  const fileReader = new FileReader();
  fileReader.onload = () => {
    if (fileReader.result) {
      const config = JSON.parse(fileReader.result);
      cimSettingInstance.entityApi.addEntity(config);
      const entity = cimSettingInstance.entityApi.getEntity(config.name);
      const parts = [];
      entity._viewComponent._part.forEach((name) => {
        const p = cimSettingInstance.api.getLayer(name);
        const p_config = p._config;
        if (!p_config.vName) {
          p_config.vName = p_config.name;
        }
        parts.push(p_config);
        cimSettingInstance.api.setOrbitControlByInstance(p, name, updateTransformPanelInfo);
      });
      const particles = [];
      entity._viewComponent._particle.forEach((name) => {
        const pl = cimSettingInstance.api.getLayer(name);
        const pd = cimSettingInstance.api.getParticleSystemData(name);
        if (!pl.vName) {
          pl.vName = pl.name;
        }
        if (!pd.vName) {
          pd.vName = pd.name;
        }
        particles.push(pd);
        cimSettingInstance.api.setOrbitControlByInstance(pl, name, updateTransformPanelInfo);
      });
      cimSettingInstance.api.setOrbitControlByInstance(entity, config.name, () => {});
      cimSettingInstance.api.refreshOrbitController();
      cimSettingInstance.api.changeOrbitControlMode('translation');
      if (currentTreeNode.value) {
        const data = {
          name: config.name,
          vName: config.vName ? config.vName : config.name,
          isDirectory: true,
          isModel: true,
          type: 'entity',
          labelColor: 'rgb(40, 200, 40)',
          children: [...parts, ...particles],
        };
        currentTreeNode.value.children.push(data);
        currentName.value = config.name;
        currentType.value = config.type;

        nextTick(() => {
          menuTree.value.setCurrentKey(config.name);
          menuTree.value.setChecked(config.name, true);
          parts.forEach((p) => {
            menuTree.value.setChecked(p.name, true);
          });
          particles.forEach((p) => {
            menuTree.value.setChecked(p.name, p.show);
          });
          const node = menuTree.value.getNode(config.name);
          modelNodes.value.push(node.data);
          handleNodeClick(data, node);
          // this.flyTo(defaultConfig.longitude, defaultConfig.latitude, defaultConfig.height + 3000)
        });
      }
    }
  };
  fileReader.onerror = (err) => {
    console.log(err);
  };
  fileReader.readAsText(file);
}

function renameParticle(node: any) {
  currentTreeNode.value = node.data;
  renameDialogVisible.value = true;
  closePopover();
}

// 删除树节点及特效实例
async function deleteEffectAndTreeNode(node: any) {
  currentTreeNode.value = node.data;
  closePopover();
  const message = `确认删除该${currentTreeNode.value.isDirectory ? '实体' : '特效'}？`;
  const result = await confirmAgain('提示', message);
  if (!result) return;
  // const targetIndex = treeData.value[0].children.findIndex(item => item.name === currentName.value)
  menuTree.value.remove(currentTreeNode.value);
  removeParticleSystem(currentTreeNode.value);
  // currentTreeNode恢复为根节点
  currentTreeNode.value = treeData.value[0];
  messageUtil.success('删除成功！');
}

// 递归创建粒子系统
function createParticleSystem(treeData: any[]) {
  const { cimSettingInstance } = window;
  if (Array.isArray(treeData)) {
    treeData.forEach((node) => {
      if (node.type) {
        cimSettingInstance.api.createParticleSystem(node);
      }
      if (node.children && node.children.length > 0) {
        createParticleSystem(node.children);
      }
    });
  }
}

// 递归销毁特效实例
function removeParticleSystem(treeNodeData: any) {
  const { cimSettingInstance } = window;
  cimSettingInstance.api.changeOrbitControlMode('');
  if (treeNodeData.isModel) {
    cimSettingInstance.entityApi.removeEntity(treeNodeData.name);
    treeNodeData.children.forEach((child: any) => {
      cimSettingInstance.api.deleteOrbitControl(child.name);
    });
  } else if (treeNodeData.isDirectory) {
    treeNodeData.children.forEach((nodeData: any) => {
      removeParticleSystem(nodeData);
    });
  } else {
    switch (treeNodeData.type) {
      case 'gltfmodel':
        cimSettingInstance.entityApi.deletePartInEntity(treeNodeData.name, treeNodeData.owner);
        const p = cimSettingInstance.api.getLayer(treeNodeData.name);
        cimSettingInstance.api.destroyLayer(treeNodeData.name);
        break;
      default:
        cimSettingInstance.api.removeParticleSystem(treeNodeData.name);
        break;
    }
  }
  cimSettingInstance.api.deleteOrbitControl(treeNodeData.name);
}

// 填充属性面板数据
function assignPanelData(data?: any) {
  const { cimSettingInstance } = window;
  let effectAttributes;
  if (data) {
    effectAttributes = data;
  } else {
    effectAttributes = cimSettingInstance.api.getLayer(currentName.value)?.config;
  }
  switch (effectAttributes.type) {
    case EffectType.CIMPARTICLESYSTEM:
    case EffectType.U3DPARTICLESYSTEM:
      Object.assign(cimPanelData.value, effectAttributes);
      if (effectAttributes.offset === undefined) {
        effectAttributes.offset = [0, 0, 0];
        cimPanelData.value.offset[0] = 0;
        cimPanelData.value.offset[1] = 0;
        cimPanelData.value.offset[2] = 0;
      }
      if (effectAttributes.gravityX === undefined) {
        effectAttributes.gravityX = 0;
        effectAttributes.gravityY = 0;
        effectAttributes.gravityZ = 0;
        cimPanelData.value.gravityX = 0;
        cimPanelData.value.gravityY = 0;
        cimPanelData.value.gravityZ = 0;
      }
      break;
    case EffectType.FAKEPARTICLESYSTEM:
      Object.assign(fakePanelData.value, effectAttributes);
      if (effectAttributes.offset === undefined) {
        effectAttributes.offset = [0, 0, 0];
        fakePanelData.value.offset[0] = 0;
        fakePanelData.value.offset[1] = 0;
        fakePanelData.value.offset[2] = 0;
      }
      break;
  }
}

function updateEffect() {
  const { cimSettingInstance } = window;
  // 更新树节点treeData
  const currentNodeData = menuTree.value.getCurrentNode();
  const particlesData = cimSettingInstance.api.getLayer(currentName.value).config;
  switch (currentType.value) {
    case EffectType.CIMPARTICLESYSTEM:
    case EffectType.U3DPARTICLESYSTEM:
      Object.assign(particlesData, cimPanelData.value);
      cimSettingInstance.api.replaceParticleSystem(currentName.value, particlesData);
      break;
    case EffectType.FAKEPARTICLESYSTEM:
      Object.assign(particlesData, fakePanelData.value);
      cimSettingInstance.api.replaceParticleSystem(currentName.value, particlesData);
      break;
  }
  nextTick(() => {
    const node = menuTree.value.getNode(currentName.value);
    handleNodeClick(particlesData, node);
  });
}

function updateTransformPanelInfo(info: any) {
  const { mode, position, rotation, scale, cartographic, offset } = info;
  cimPanelData.value.position_x = position.x;
  cimPanelData.value.position_y = position.y;
  cimPanelData.value.position_z = position.z;

  cimPanelData.value.rotation_x = Number(rotation.x.toFixed(1));
  cimPanelData.value.rotation_y = Number(rotation.y.toFixed(1));
  cimPanelData.value.rotation_z = Number(rotation.z.toFixed(1));

  cimPanelData.value.scale_x = scale.x;

  cimPanelData.value.latitude = Number(cartographic.latitude.toFixed(6));
  cimPanelData.value.longitude = Number(cartographic.longitude.toFixed(6));
  cimPanelData.value.height = Number(cartographic.height.toFixed(6));
  if (offset) {
    cimPanelData.value.offset = offset;
    fakePanelData.value.offset = offset;
  }
}

function updateTransform(dataAttr: any) {
  const { cimSettingInstance } = window;
  cimPanelData.value = dataAttr;
  const position = {
    x: cimPanelData.value.longitude,
    y: cimPanelData.value.latitude,
    z: cimPanelData.value.height,
  };
  const rotation = {
    x: cimPanelData.value.rotation_x,
    y: cimPanelData.value.rotation_y,
    z: cimPanelData.value.rotation_z,
  };
  const scale = cimPanelData.value.scale_x;
  cimSettingInstance.api.updateEffectModel({
    position,
    rotation,
    scale,
  });
  cimSettingInstance.api.refreshOrbitController();
}

// dialog选择新添加特效类型
function addParticleDialogClose(flag = false, type: string) {
  if (flag) {
    currentType.value = type;
    addEffectAndTreeNode();
  }
  dialogVisible.value = false;
}

// 重命名确认
function renameDialogClose(flag = false, name: string) {
  const { cimSettingInstance } = window;
  if (flag) {
    const layer = cimSettingInstance.api.getLayer(currentTreeNode.value.name);
    layer.config.vName = name;
    currentTreeNode.value.vName = name;
  }
  renameDialogVisible.value = false;
}

// 添加模型
function addModel() {
  window.parent.postMessage({ type: 'addModel', data: '' }, '*');
}

function toggleTreeVisible() {
  treeVisible.value = !treeVisible.value;
}

function toggleAttrVisible() {
  attrVisible.value = !attrVisible.value;
}

function addParticle(node: any) {
  currentName.value = node.data.name;
  currentType.value = node.data.type;
  currentMoreNode = node.data;
  dialogVisible.value = true;
  closePopover();
}

// 树递归获取最大编号
function getMaxCimNumber(arr: any) {
  arr.forEach((item: any) => {
    const currNameArr = item.vName.split(/^粒子/);
    if (currNameArr.length === 2 && currNameArr[1] !== '' && !isNaN(currNameArr[1])) {
      const currNumber = Number(currNameArr[1]);
      if (currNumber > cimNumber) {
        cimNumber = currNumber;
      }
    }
    if (item.children) {
      getMaxCimNumber(item.children);
    }
  });
}

function choosePicClose() {
  choosePicVisible.value = false;
}

function picChange(src: string) {
  choosePicClose();
  cimPanelData.value.image = src;
  updateEffect();
}

function chooseModelClose() {
  chooseResourceVisible.value = false;
}

function validateEffectInfo() {
  if (!effectInfo.value.effectName) {
    messageUtil.warning('请填写特效名称');
    return false;
  }
  if (!effectInfo.value.effCover) {
    messageUtil.warning('请设置特效封面');
    return false;
  }
  if (!treeData.value[0].children.length) {
    messageUtil.warning('实体请添加粒子效果');
    return false;
  }
  saveJSON(treeData.value[0]);
  return {
    form: effectInfo.value,
    json: exportJson,
  };
}

const beforeCoverUpload = async (file: any) => {
  const fileSuffix = file.name.substring(file.name.lastIndexOf('.'));
  if (!['.jpg', '.png'].includes(fileSuffix)) {
    messageUtil.warning('封面格式错误');
    return false;
  }
  const res: CommonUploadResp = await commonUpload(file);
  effectInfo.value.effCover = res.files[0].refAddress;
  return false;
};

function quickOperation(e?: any) {
  const { cimSettingInstance } = window;
  // 判断焦点元素
  if (document.activeElement!.localName !== 'body') {
    return;
  }
  if (e.code === 'KeyW') {
    cimSettingInstance.api.changeOrbitControlMode('translation');
  }
  if (e.code === 'KeyE') {
    cimSettingInstance.api.changeOrbitControlMode('rotation');
  }
  if (e.code === 'KeyR') {
    cimSettingInstance.api.changeOrbitControlMode('scale');
  }
  if (e.code === 'KeyQ') {
    cimSettingInstance.api.changeOrbitControlMode('none');
  }
  if (e.ctrlKey && e.key === 'z') {
    cimSettingInstance.api.undoOrbitControl();
  }
}

// 关闭popover
function closePopover() {
  opePopoverRefs.forEach((v: any) => v.hide());
}

defineExpose({ validateEffectInfo });
</script>

<style lang="less">
.el-popover.el-popper.particle-ope-popover {
  padding: 0px;
  background: #0a0b0d;
  border: 1px solid rgba(151, 151, 151, 0.3);
  .el-popper__arrow {
    &::before {
      border-color: rgba(151, 151, 151, 0.3);
      background: #0a0b0d;
    }
  }
  .node-ope-ul {
    padding: 10px;
    .node-ope-li {
      display: flex;
      align-items: center;
      height: 32px;
      padding: 0 10px;
      color: #fff;
      cursor: pointer;
      &:hover {
        background: fade(#33373f, 32%);
        color: #0560fd;
      }
    }
  }
}
</style>
<style lang="less" scoped>
.particle-setting-iframe {
  flex: 1;
  #particleCesiumContainerBox {
    height: calc(100vh - 60px);
  }
  .tree-panel-wrap {
    width: 356px;
    left: -356px;
    .panel {
      &-header {
        height: 0px;
      }
      &-handler {
        right: -28px !important;
      }
      &-body {
        height: 100% !important;
        padding: 10px;
      }
    }
    &.active {
      left: 0px;
      .panel-handler .svg-icon {
        transform: rotate(0deg);
      }
    }
  }
  .effect-basic-wrap {
    .panel {
      &-body {
        padding: 20px 22px 0 18px;
        .el-form {
          width: 100%;
          .cover-wrap {
            display: flex;
            flex-direction: column;
            width: 100%;
            .upload-wrap {
              position: relative;
              .hover-wrap {
                position: absolute;
                left: 0;
                top: 0;
                width: 100%;
                height: 100%;
                display: none;
                justify-content: center;
                align-items: center;
                background: fade(#202329, 68%);
                .el-button {
                  width: 65px;
                  border-color: #ff5454;
                  color: #ff5454;
                }
              }
              &:hover {
                .hover-wrap {
                  display: flex;
                }
              }
            }
            :deep(.el-upload) {
              width: 100%;
            }
            .cover {
              display: flex;
              justify-content: center;
              align-items: center;
              width: 100%;
              height: 136px;
              background: #202329;
              border-radius: 4px;
              border: 1px solid rgba(69, 72, 81, 0.6);
              overflow: hidden;
              .svg-icon {
                font-size: 42px;
                margin-right: 0px;
              }
              .el-image {
                width: 100%;
                height: 100%;
              }
            }
            .cover-tip {
              margin: 10px 0;
              font-size: 12px;
              color: #9a9a9a;
              line-height: initial;
            }
            .el-button {
              width: 140px;
              height: 36px;
            }
          }
        }
      }
    }
  }
  .attr-wrap {
    width: 650px;
    left: calc(100vw);
    .panel {
      &-handler {
        left: -28px;
      }
      &-body {
        height: 100% !important;
        padding: 20px;
      }
    }
    &.active {
      left: calc(100vw - 650px);
      .panel-handler .svg-icon {
        transform: rotate(180deg);
      }
    }
  }
}
.panel-wrapper {
  position: absolute;
  top: 60px;
  left: calc(100vw - 336px);
  width: 336px;
  height: calc(100% - 60px);
  background: #171a1f;
  transition: left 0.4s ease-out;
  z-index: 2;
  .panel-inner {
    position: relative;
    height: 100%;
    .panel-handler {
      position: absolute;
      top: 0;
      right: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      width: 28px;
      height: 28px;
      font-size: 18px;
      border-radius: 4px;
      background: var(--el-color-primary);
      cursor: pointer;
    }
    .panel {
      height: 100%;
      .panel-header {
        height: 28px;
        line-height: 28px;
        font-size: 14px;
        text-align: center;
        background: #0a0b0d;
      }

      .panel-body {
        display: flex;
        height: calc(100% - 28px);
        overflow-y: auto;
        .el-tabs {
          display: flex;
          flex-direction: column;
          width: 100%;
          background: transparent;
          border-color: transparent;
          :deep(.el-tabs__header) {
            background: #414448;
            border: none;
          }
          :deep(.el-tabs__content) {
            height: 100%;
            overflow-y: auto;
          }
          :deep(.el-tabs__item) {
            border: none;
            &.is-active {
              background: #000;
            }
          }
        }
      }
    }
  }
  :deep(.el-loading-mask) {
    background-color: transparent;
    .circular {
      width: 24px;
    }
  }
}
:deep(.el-tree) {
  width: 100%;
  background: #171a1f;
  color: #fff;
  .el-tree-node {
    &__content {
      .custom-tree-node {
        width: 100%;
        display: flex;
        align-items: center;
        .more-wrap {
          margin-left: auto;
          margin-right: 25px;
          .svg-icon {
            font-size: 16px;
          }
        }
      }
      &:hover,
      &:focus {
        background: rgba(5, 96, 253, 0.2);
        border-radius: 4px;
        border: 1px solid #0560fd;
      }
    }
    &.is-current {
      & > .el-tree-node__content {
        background: rgba(5, 96, 253, 0.2);
        border-radius: 4px;
        border: 1px solid #0560fd;
      }
    }
  }
}

.attr-wrap {
  :deep(.el-form) {
    &-item {
      .row-item {
        width: 100%;
        display: flex;
        margin-bottom: 10px;
        &-col {
          flex: 1;
          display: flex;
          margin-right: 22px;
          &.column {
            flex-direction: column;
            .title {
              flex: 0 0 auto;
            }
          }
          &:last-child {
            margin-right: 0px;
          }
        }
        .title {
          flex: 0 0 40px;
        }
        .content {
          display: flex;
          align-items: center;
        }
        .el-input {
          flex: 1;
        }
        .unit {
          flex: 0 0 24px;
          text-align: center;
        }
        &:last-child {
          margin-bottom: 0px;
        }
      }
    }
  }
}

.screenShotIcon {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: calc(100vw - 200px);
  height: calc(100vh - 400px);
}
</style>
